/** 
    @file pixelmap.hpp
    @brief Game map
*/

#ifndef PM3_PIXELMAP_HPP
#define PM3_PIXELMAP_HPP

#include "lua/lua_scripted.hpp"
#include "pixellayer.hpp"
#include "pixelobj.hpp"
#include "pixelplayer.hpp"
#include "pixelmail.hpp"
#include "util.hpp"
#include "portal_debug.hpp"

#include <set>

//! Pixelmessage to load the next map
#define PM_NEXT_MAP "World.NextMap"

namespace pm3 {
    /**
        @class PIXELMAP
        @brief Game map
    */
    class PIXELMAP: public LUA_SCRIPTED {
    friend class PIXELWORLD;
    public:
            /**
                @brief Constructor
            */
            PIXELMAP();
            /**
                @brief Destructor
            */
            ~PIXELMAP();

            /**
                @brief Assignment operator
            */
            PIXELMAP& operator=( const PIXELMAP &r_map );

            /**
                @brief Unloads the map
            */
            void unload();

            /**
                @brief Finds path to the closest objective
                @return Vector of position markers
            */
            std::vector<Ogre::Vector3> find_path() const;

            /**
                @brief Finds a startpoint
                @return Ogre viewpoint
            */
            Ogre::ViewPoint find_start() const;

            /**
                @brief Finds a startpoint by number
                @param[in] i_id Number of the startpoint
                @return Ogre viewpoint
            */
            Ogre::ViewPoint get_start( int i_id ) const;

            /**
                @brief Gets the number of startpoints
                @return Number of startpoints
            */
            inline int get_num_starts() const {
                return m_startpoints.size();
            }

            /**
                @brief Gets the size of a map room
                @return Number of startpoints
            */
            inline Ogre::Real get_room_size() const {
                return m_size;
            }

            /**
                @brief Sets the size of a map room
                @param[in] f_size New room size
            */
            inline void set_room_size( Ogre::Real f_size ) {
                m_size = f_size;
            }

            /**
                @brief Fetches the list of tags that corresponds to a specific pixel by color
                @param[in] i_layer Layer index or pixel height (Z coordinate)
                @param[in] i_x X-coordinate in pixels or rooms
                @param[in] i_y Y-coordinate in pixels or rooms
                @return The list of tags associated with pixel color
            */
            std::string get_tags( int i_layer, int i_x, int i_y );

            /**
                @brief Fetches the list of tags that corresponds to a specific pixel color
                @param[in] r_col Reference to the color
                @return The list of tags associated with this pixel color
            */
            std::string get_tags( const Ogre::ColourValue &r_col );

            /**
                @brief Checks if a specific pixel features a tag
                @param[in] i_layer Layer index or pixel height (Z coordinate)
                @param[in] i_x X-coordinate in pixels or rooms
                @param[in] i_y Y-coordinate in pixels or rooms
                @param[in] r_tag Reference of the tag to be checked
                @return True if it does feature the tag, otherwise false
            */
            bool check_tag( int i_layer, int i_x, int i_y, const std::string &r_tag );

            /**
                @brief Gets the possible directions from a room or pixel
                @param[in] i_layer Layer index or pixel height (Z coordinate)
                @param[in] i_x X-coordinate in pixels or rooms
                @param[in] i_y Y-coordinate in pixels or rooms
                @return Directions ORed together
            */
            int get_directions( int i_layer, int i_x, int i_y );
            /**
                @brief Gets the possible directions from a room or pixel
                @param[in] i_layer Layer index or pixel height (Z coordinate)
                @param[in] i_x X-coordinate in pixels or rooms
                @param[in] i_y Y-coordinate in pixels or rooms
                @return A string containing the directions
            */
            inline std::string get_directions_str( int i_layer, int i_x, int i_y ) {
                return pm3::util::directions_to_str( get_directions( i_layer, i_x, i_y ) );
            }

            /**
                @brief Gets a pointer to the room on a specific pixel
                @param[in] i_layer Layer index or pixel height (Z coordinate)
                @param[in] i_x X-coordinate in pixels or rooms
                @param[in] i_y Y-coordinate in pixels or rooms
                @return Pointer to the pixelroom or NULL in case there are none
                @note Instead of asking for object pointer from a room (might be NULL), use get_obj
                @see get_obj, pm3::PIXELNODE::is_initialized
            */
            PIXELROOM *get_room( int i_layer, int i_x, int i_y );

            /**
                @brief Gets a pointer to the zone on a specific pixel
                @param[in] i_layer Layer index or pixel height (Z coordinate)
                @param[in] i_x X-coordinate in pixels or rooms
                @param[in] i_y Y-coordinate in pixels or rooms
                @return Pointer to the zone or NULL in case there are none
            */
            Ogre::PCZone *get_zone( int i_layer, int i_x, int i_y );

            /**
                @brief Gets a pointer to a neighbour of a room on a specific pixel
                @param[in] i_layer Layer index or pixel height (Z coordinate)
                @param[in] i_x X-coordinate in pixels or rooms
                @param[in] i_y Y-coordinate in pixels or rooms
                @param[in] i_dir Direction of the neighbour
                @return Pointer to the pixelroom or NULL in case there are none
            */
            PIXELROOM *get_neighbour( int i_layer, int i_x, int i_y, int i_dir );

            /**
                @brief Gets a pointer to an object by its ID
                @param[in] i_id Object ID
                @return Pointer to the object or NULL if it doesn't exist
            */
            PIXELOBJ *get_obj( int i_id ) const;

            /**
                @brief Gets a pointer to the object on a specific pixel
                @param[in] i_layer Layer index or pixel height (Z coordinate)
                @param[in] i_x X-coordinate in pixels or rooms
                @param[in] i_y Y-coordinate in pixels or rooms
                @return Pointer to the pixelobject or NULL in case there are none
            */
            PIXELOBJ *get_obj( int i_layer, int i_x, int i_y );

            /**
                @brief Gets a pointer to an object by its color and directions
                @param[in] r_col Reference to pixel color
                @param[in] i_dirs Direction flags
                @return Pointer to the object or NULL if it doesn't exist
            */
            PIXELOBJ *get_obj( const Ogre::ColourValue &r_col, int i_dirs );

            /**
                @brief Gets the number of objects
                @return Number of objects
            */
            inline int num_objects() const {
                return m_objects.size();
            }

            /**
                @brief Gets a pointer to a layer by its ID
                @param[in] i_id Layer ID
                @return Pointer to the layer or NULL if it doesn't exist
            */
            PIXELLAYER *get_layer( int i_id ) const;

            /**
                @brief Returns a pointer to the layer that the point is closest to
                @param[in] r_point Reference to the point that's checked
                @return Pointer to the closest layer
            */
            PIXELLAYER *get_layer( const Ogre::Vector3 &r_point ) const;

            /**
                @brief Gets the number of layers
                @return Number of layers
            */
            inline int num_layers() const {
                return m_layers.size();
            }

            /**
                @brief Converts from grid position to world position
                @param[in] r_grid Reference to grid position
                @return World position
            */
            Ogre::Vector3 grid_to_world( const Ogre::Vector3 &r_grid ) const;

            /**
                @brief Converts from world position to grid position
                @param[in] r_world Reference to world position
                @return Grid position
            */
            Ogre::Vector3 world_to_grid( const Ogre::Vector3 &r_world ) const;

            /**
                @brief Updates the map
                @param[in] r_player_grid_pos Reference to player grid position
            */
            void update( const Ogre::Vector3 &r_player_grid_pos );

            /**
                @brief Adds a layer to the map
                @param[in] r_path Reference to the layerscript
                @return Layer index
            */
            int add_layer( const std::string &r_path );

            /**
                @brief Adds an empty layer to the map
                @return Layer index
            */
            int new_layer();

            /**
                @brief Adds an object to the map
                @param[in] r_color Reference to the color of the pixel that the object corresponds to
                @param[in] r_dirs Reference to a string with room exits (pixel directions)
                @param[in] r_path Reference to objectscript path
                @return Object index
            */
            int add_object( const Ogre::ColourValue &r_color, const std::string &r_dirs, const std::string &r_path );

            /**
                @brief Adds a color to the map
                @param[in] r_color Reference to the color to be added
                @param[in] r_tags Reference to a list of tags to be associated with the color
                @return Color index
            */
            int add_color( const Ogre::ColourValue &r_color, const std::string &r_tags );

            /**
                @brief Dumps the map into the log
            */
            void dump();

            /**
                @brief Saves map script As
                @param[in] r_path Path to the script
                @return Path to the script on success, empty string on failure
            */
            std::string save_script( const std::string &r_path );

            /**
                @brief Saves map script
                @return Path to the script on success, empty string on failure
            */
            inline std::string save_script() {
                return save_script( m_script_path );
            }

            /**
                @brief Sets layer rendering mode
                @param[in] e_mode Rendering mode
            */
            void set_render_mode( pm3::RENDER_MODE e_mode );

            /**
                @brief Gets layer rendering mode
                @return Rendering mode
            */
            inline pm3::RENDER_MODE get_render_mode() const {
                return m_render_mode;
            }

            /**
                @brief Gets a pixel on the map
                @param[in] r_pos Reference to the grid position of the pixel
                @return Pixel color
            */
            Ogre::ColourValue get_pixel( const Ogre::Vector3 &r_pos ) const;

            /**
                @brief Sets a pixel on the map
                @param[in] r_pos Reference to the grid position of the pixel
                @param[in] r_color Reference to the color of the pixel
                @return True on success, false on failure
            */
            bool set_pixel( const Ogre::Vector3 &r_pos, const Ogre::ColourValue &r_color );

            /**
                @brief Gets the number of colors
                @return Number of colors on the map
            */
            inline int num_colors() const {
                return m_colortags.size();
            }

            /**
                @brief Gets a specific color from the map palette
                @return Color
            */
            Ogre::ColourValue get_color( int i_id );

            /**
                @brief Shows or hides debug boxes
                @param[in] b_show To show or hide?
            */
            void show_debug_boxes( bool b_show );

            /**
                @brief Checks if debug boxes are shown
                @return True or false
            */
            inline bool debug_boxes_shown() const {
                return m_show_debug;
            }

    protected:
            //! Map index
            int m_id;

            //! Map rendering mode
            pm3::RENDER_MODE m_render_mode;
            //! Show debug boxes
            bool m_show_debug;

            /**
                @class PIXEL
                @brief Pixel information class
            */
            class PIXEL {
            public:
                /**
                    @brief Constructor
                */
                PIXEL() {
                    m_directions = 0;
                }

                /**
                    @brief Destructor
                */
                ~PIXEL() {}

                //! Pixel color
                Ogre::ColourValue m_color;
                //! Directions based on neighbor pixels
                int m_directions;

                /**
                    @brief Assignment operator
                */
                PIXEL& operator=( const PIXEL &r_pix ) {
                    m_color = r_pix.m_color;
                    m_directions = r_pix.m_directions;
                    return *this;
                }

                /**
                    @brief Comparison (LT) operator
                */
                bool operator<( const PIXEL &r_pix ) const {
                    if (m_color.getAsRGBA() < r_pix.m_color.getAsRGBA())
                        return true;
                    else if (m_color.getAsRGBA() == r_pix.m_color.getAsRGBA())
                        return (m_directions < r_pix.m_directions);
                    return false;
                }

                /**
                    @brief Comparison (EQ) operator
                */
                bool operator==( const PIXEL &r_pix ) const {
                    return (m_color.getAsRGBA() == r_pix.m_color.getAsRGBA() && m_directions == r_pix.m_directions);
                }
            };

            class OBJ_COMPARE {
                public:
                    bool operator() ( const PIXELOBJ *p_a, const PIXELOBJ *p_b ) {
                        if (!p_a || !p_b)
                            return false;
                        if (p_a->get_directions() < p_b->get_directions()) {
                            return true;
                        } else if (p_a->get_directions() > p_b->get_directions()) {
                            return false;
                        } else {
                            if (p_a->get_color().getAsRGBA() < p_b->get_color().getAsRGBA())
                                return true;
                        }
                        return false;
                    }
            };

            /**
                @brief Binds functions called by lua
            */
            void bind();

            /**
                @brief Initializes the map with all its layers
            */
            void init();

            //! Map name
            std::string m_name;

            //! Map room size
            Ogre::Real m_size;

            //! Map layers
            std::vector<PIXELLAYER *> m_layers;

            //! Map zones
            std::vector<Ogre::PCZone *> m_zones;

            //! Map room objects
            std::set<PIXELOBJ *, OBJ_COMPARE> m_objects;
            //! Map room objects by pixel color and directions
            std::map<PIXEL, PIXELOBJ *> m_colormap;
            //! Tags corresponding to pixel colors
            std::map<Ogre::RGBA, std::string> m_colortags;

            //! Startpoints
            std::vector<Ogre::ViewPoint> m_startpoints;

            //! Old player gridpos
            Ogre::Vector3 m_old_player_grid_pos;

            /**
                @brief Sets up a layer by setting all the room and object pointers and finding startingpoints
                @param[in] i_layer Id of the layer to be set up
            */
            void setup_layer( int i_layer );

            /**
                @brief Initializes and connects all room portals
            */
            void setup_portals();

            /**
                @brief Renders debug items
            */
            void debug_render();

            /**
                @brief Sets up a room
                @param[in] p_room Pointer to the room
                @return True on success, false on failure
            */
            bool room_setup( PIXELROOM *p_room );

            /**
                @brief Adds a zone to a room
                @param[in] p_room Pointer to the room
                @return Pointer to the zone on success, NULL on failure
            */
            Ogre::PCZone *room_zone_setup( PIXELROOM *p_room );

            /**
                @brief Pairs room portals with adjacent rooms
                @param[in] p_room Pointer to the room
                @return True on success, false on failure
            */
            bool room_pair_portals( PIXELROOM *p_room );

            /**
                @brief Updates a room (makes sure it corresponds to the pixel it's on)
                @param[in] p_room Pointer to the room to be updated
            */
            void update_room( PIXELROOM *p_room );

            /**
                @brief Adds a startpoint
                @param[in] r_vpoint Reference to the startpoint
                @return True if the startpoint was added, false if such a startpoint already existed
            */
            bool add_startpoint( const Ogre::ViewPoint &r_vpoint );

            /**
                @brief Removes a startpoint
                @param[in] r_point Reference to the start position
                @return True if the startpoint was removed, false otherwise
            */
            bool remove_startpoint( const Ogre::Vector3 &r_point );
    };

    namespace lua {
        //! Pointer to the currently active map to which all lua functions apply
        extern PIXELMAP *g_active_map;
    }
}

#endif

